(function(){
  const logEl = document.getElementById('log');
  const btnConnect = document.getElementById('btnConnect');
  const btnDisconnect = document.getElementById('btnDisconnect');
  const btnFormat = document.getElementById('btnFormat');
  const btnTokens = document.getElementById('btnTokens');

  const uri = 'file:///demo.txt';
  let ws = null;
  let nextId = 1;
  let version = 1;
  let connected = false;
  let decorations = [];
  const pending = new Map();

  function log(...args){
    const s = args.map(a => typeof a === 'string' ? a : JSON.stringify(a, null, 2)).join(' ');
    const atBottom = Math.abs(logEl.scrollHeight - logEl.scrollTop - logEl.clientHeight) < 4;
    logEl.textContent += s + '\n';
    if (atBottom) logEl.scrollTop = logEl.scrollHeight;
    console.log('[web]', ...args);
  }

  function send(obj){
    if (!ws || ws.readyState !== WebSocket.OPEN) return;
    ws.send(JSON.stringify(obj));
  }

  function request(method, params){
    const id = nextId++;
    return new Promise((resolve, reject) => {
      pending.set(id, { resolve, reject, method });
      send({ jsonrpc: '2.0', id, method, params });
    });
  }
  function notify(method, params){
    send({ jsonrpc: '2.0', method, params });
  }

  function currentText(){
    const editor = window.editor;
    return editor && editor.getModel() ? editor.getModel().getValue() : '';
  }
  function setText(newText){
    const editor = window.editor;
    if (editor && editor.getModel()) editor.getModel().setValue(newText);
  }

  function connect(){
    if (connected) return;
    ws = new WebSocket('ws://localhost:3001');
    ws.onopen = () => {
      connected = true;
      log('WS connected');
      request('initialize', {
        processId: null,
        rootUri: null,
        capabilities: {},
        workspaceFolders: null,
      }).then((initResult) => {
        log('Server capabilities:', initResult.capabilities);
        notify('initialized', {});
        // didOpen with Monaco content
        notify('textDocument/didOpen', {
          textDocument: {
            uri,
            languageId: 'plaintext',
            version,
            text: currentText(),
          }
        });
      });

      ws.onmessage = (ev) => {
        try {
          const msg = JSON.parse(ev.data);
          if (msg.method === 'textDocument/publishDiagnostics') {
            log('Diagnostics:', msg.params.diagnostics);
          }
          if (msg.id !== undefined) {
            const p = pending.get(msg.id);
            if (p) {
              pending.delete(msg.id);
              p.resolve(msg.result);
            }
          }
        } catch (e) {
          log('Message parse error:', e);
        }
      };

      ws.onclose = () => { connected = false; log('WS closed'); };
      ws.onerror = (e) => { log('WS error', e); };

      // 监听编辑器内容变化，增量同步（简化为全量文本覆盖）
      const editor = window.editor;
      if (editor) {
        editor.onDidChangeModelContent(debounce(() => {
          version++;
          notify('textDocument/didChange', {
            textDocument: { uri, version },
            contentChanges: [{ text: currentText() }]
          });
        }, 200));
      }
    };
  }

  function disconnect(){
    if (!ws) return;
    try {
      request('shutdown', {});
      notify('exit');
      ws.close();
    } catch (e) {}
    connected = false;
  }

  function format(){
    request('textDocument/formatting', {
      textDocument: { uri },
      options: { tabSize: 2, insertSpaces: true }
    }).then(edits => {
      if (Array.isArray(edits) && edits.length && edits[0].newText !== undefined) {
        setText(edits[0].newText);
        version++;
        notify('textDocument/didChange', {
          textDocument: { uri, version },
          contentChanges: [{ text: currentText() }]
        });
        log('Applied formatting');
      } else {
        log('No formatting edits');
      }
    });
  }

  function tokens(){
    request('textDocument/semanticTokens/full', { textDocument: { uri } })
      .then((res) => {
        if (!res || !res.data) { log('No tokens'); return; }
        const legend = { tokenTypes: ['keyword','comment'], tokenModifiers: ['deprecated','modification','documentation','readonly'] };
        const decoded = decodeSemanticTokens(legend, res.data);
        log('Semantic tokens (decoded):', decoded);
        highlightTokens(decoded);
      });
  }

  function decodeSemanticTokens(legend, data){
    const out = [];
    let line = 0, char = 0;
    for (let i = 0; i < data.length; i += 5) {
      const lineDelta = data[i];
      const charDelta = data[i+1];
      const length = data[i+2];
      const tokenTypeIndex = data[i+3];
      const tokenModsBits = data[i+4];
      line += lineDelta;
      char = lineDelta === 0 ? char + charDelta : charDelta;
      const type = legend.tokenTypes[tokenTypeIndex] || `type#${tokenTypeIndex}`;
      const mods = [];
      for (let b = 0; b < legend.tokenModifiers.length; b++) {
        if (tokenModsBits & (1 << b)) mods.push(legend.tokenModifiers[b]);
      }
      out.push({ line, character: char, length, type, modifiers: mods });
    }
    return out;
  }

  function highlightTokens(tokens){
    const monaco = window.monaco;
    const editor = window.editor;
    if (!editor || !monaco) return;
    const decos = tokens.map(t => {
      const range = new monaco.Range(t.line + 1, t.character + 1, t.line + 1, t.character + 1 + t.length);
      const cls = tokenClass(t);
      return { range, options: { inlineClassName: cls } };
    });
    decorations = editor.deltaDecorations(decorations, decos);
  }

  function tokenClass(t){
    if (t.type === 'keyword') {
      if (t.modifiers.includes('readonly')) return 'tok-keyword-readonly';
      if (t.modifiers.includes('modification')) return 'tok-keyword-mod';
      return 'tok-keyword';
    }
    if (t.type === 'comment') {
      if (t.modifiers.includes('documentation')) return 'tok-comment-doc';
      return 'tok-comment';
    }
    return 'tok-keyword';
  }

  // 注册 Hover 与 Completion provider（等 Monaco 就绪）
  function initMonacoProviders(){
    const monaco = window.monaco;
    if (!monaco) return;
    monaco.languages.registerHoverProvider('plaintext', {
      provideHover: (model, position) => {
        if (!ws || ws.readyState !== WebSocket.OPEN) return null;
        const params = {
          textDocument: { uri },
          position: { line: position.lineNumber - 1, character: position.column - 1 }
        };
        return request('textDocument/hover', params).then((res) => {
          if (!res) return null;
          let value = '';
          if (typeof res.contents === 'string') value = res.contents;
          else if (Array.isArray(res.contents)) value = res.contents.map(c => typeof c === 'string' ? c : (c.value || '')).join('\n');
          else if (res.contents && res.contents.value) value = res.contents.value;
          const r = res.range ? new monaco.Range(res.range.start.line + 1, res.range.start.character + 1, res.range.end.line + 1, res.range.end.character + 1)
                              : new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column);
          return { contents: [{ value }], range: r };
        });
      }
    });

    monaco.languages.registerCompletionItemProvider('plaintext', {
      triggerCharacters: ['T','F','N','D'],
      provideCompletionItems: (model, position) => {
        const params = {
          textDocument: { uri },
          position: { line: position.lineNumber - 1, character: position.column - 1 }
        };
        return request('textDocument/completion', params).then((items) => {
          const arr = items && items.items ? items.items : items || [];
          const suggestions = arr.map((it) => ({
            label: it.label,
            kind: monaco.languages.CompletionItemKind.Keyword,
            insertText: it.insertText || it.label,
            detail: it.detail,
            documentation: it.documentation && (typeof it.documentation === 'string' ? it.documentation : it.documentation.value),
          }));
          return { suggestions };
        });
      }
    });
  }

  function debounce(fn, ms){
    let t = null;
    return function(){
      clearTimeout(t);
      t = setTimeout(fn, ms);
    };
  }

  btnConnect.addEventListener('click', connect);
  btnDisconnect.addEventListener('click', disconnect);
  btnFormat.addEventListener('click', format);
  btnTokens.addEventListener('click', tokens);
  window.addEventListener('monaco_ready', initMonacoProviders);
})();